define('core/ajax/Request', [ 'core/jQuery', 'core/URI', 'core/ajax/ResponseCache' ], function(require, exports, module) {

    var $ = require('core/jQuery'),
        URI = require('core/URI'),
        ResponseCache = require('core/ajax/ResponseCache');

    var Cache = {};

    var Request = function(uri) {
        if (uri) {
            this.setURI(uri);
        }
        this._method = 'GET';
        this._dataType = 'json';
        this._data = {};
        this._options = {};
        this._xhr = null;
    };

    K.mix(Request, {
        load: function(url, element) {
            var $element = $(element),
                method = $element.attr('data-ajax') == 'request-post' ? 'POST' : 'GET';

            var loadingElement = $element.find('._j_loading_elem');
            if (! loadingElement.length) {
                loadingElement = $element;
            }

            if (loadingElement.hasClass('loading')) {
                return;
            }

            var request = new Request(url)
                .setMethod(method)
                .setRelativeTo(element)
                .setLoadingElement(loadingElement)
                .send();
        },

        invalidate: function(url) {
            return ResponseCache.invalidate(Request.normalizeURI(url));
        },

        normalizeURI: function(url, data) {
            var uri = new URI(url);
            var newQuery = {};
            // TODO: data为string?
            var query = K.mix({}, data, uri.getQueryParams());
            K.forEach(query, function(val, key) {
                if (! /^_/.test(key)) {
                    newQuery[key] = val;
                }
            });
            uri.setQueryParams(newQuery);

            return uri.setProtocol(null)
                .setDomain(null)
                .setFragment(null)
                .toString();
        }
    });

    K.mix(Request.prototype, {
        setMethod: function(method) {
            this._method = method;
            return this;
        },

        getMethod: function() {
            return this._method;
        },

        setDataType: function(dataType) {
            this._dataType = dataType;
            return this;
        },

        getDataType: function() {
            return this._dataType;
        },

        setData: function(data) {
            this._data = data;
            return this;
        },

        getData: function() {
            return this._data;
        },

        setURI: function(uri) {
            this._uri = uri;
            return this;
        },

        getURI: function() {
            return this._uri;
        },

        setHandler: function(handler) {
            if (K.isFunction(handler)) {
                this._handler = handler;
            }
            return this;
        },

        getHandler: function() {
            return this._handler;
        },

        setErrorHandler: function(errorHandler) {
            this._errorHandler = errorHandler;
            return this;
        },

        getErrorHandler: function() {
            return this._errorHandler;
        },

        setRelativeTo: function(element) {
            this._relativeTo = element;
            return this;
        },

        setFinallyHandler: function(finallyHandler) {
            this._finallyHandler = finallyHandler;
            return this;
        },

        getFinallyHandler: function() {
            return this._finallyHandler;
        },

        getRelativeTo: function() {
            return this._relativeTo;
        },

        setLoadingElement: function(element) {
            this._loadingElement = element;
            return this;
        },

        getLoadingElement: function(element) {
            return this._loadingElement;
        },

        // 目前支持 suppressEval 禁止执行
        setOption: function(name, value) {
            this._options[name] = value;
            return this;
        },

        getOption: function(name) {
            return this._options[name];
        },

        send: function() {
            if (this.getMethod() == 'GET') {
                var cachedResponse = ResponseCache.get(Request.normalizeURI(this.getURI(), this.getData()));
                if (cachedResponse) {
                    this._invokeResponseHandler(cachedResponse);
                    return;
                }
            }

            this._updateLoadingElement(true);

            var me = this;
            this._xhr = $.ajax({
                type: this._method,
                url: this._uri,
                data: this._data,
                dataType: this._dataType
            })
            .success(function(data, status, xhr) {
                if (xhr.status == 204) {
                    me._invokeResponseHandler({});
                } else {
                    if (! data.error) {
                        me._invokeResponseHandler(data);
                    } else {
                        me._invokeErrorHandler(data);
                    }
                }
            })
            .error(function(data) {
                me._invokeErrorHandler({ error: data });
            })
            .complete(function() {
                me._updateLoadingElement(false);
            });

            return this;
        },

        abort:function(){
            this._xhr && this._xhr.abort();
            return this;
        }
    });

    K.mix(Request.prototype, {
        _updateLoadingElement: function(loading) {
            if (this._loadingElement) {
                if (loading) {
                    $(this._loadingElement).addClass('loading');
                } else {
                    $(this._loadingElement).removeClass('loading');
                }
            }
        },

        _invokeResponseHandler: function(response) {
            if ('redirect' in response) {
                window.location = response.redirect;
                return;
            }
            if (this.getMethod() == 'GET' && response.cache) {
                ResponseCache.set(Request.normalizeURI(this.getURI(), this.getData()), response, 60000);
            }
            if (this.getOption("suppressEval")) {
                this._loadResource(response.resource, function() {});
                return;
            }
            if ('resource' in response) {
                this._loadResource(response.resource, K.bind(function() {
                    this._dispatchResponse(response);
                }, this));
            } else {
                this._dispatchResponse(response);
            }
        },

        _invokeErrorHandler: function(response) {
            if (this.getOption("suppressEval")) {
                return;
            }
            // TODO 区分是通讯失败 还是接口返回错误信息
            if (response && response.resource) {
                this._loadResource(response.resource, K.bind(function() {
                    this._dispatchErrorResponse(response);
                }, this));
            } else {
                this._dispatchErrorResponse(response);
            }
        },

        _loadResource: function(resource, callback) {
            var me = this;

            // 必须保证css都下载完成了 才能继续干其它事情
            var afterCssLoaded = function() {
                callback();

                if ('js' in resource && !K.isEmpty(resource.js)) {
                    K.Resource.loadJS(resource.js);
                }
                if ('map' in resource) {
                    if (resource.map.depends) {
                        K.Resource.addResourceDepends(resource.map.depends);
                    }
                    if (resource.map.uris) {
                        K.Resource.addResourceMap(resource.map.uris);
                    }
                }
                if (!me.getOption('suppressEval')) {
                    if ('onload' in resource) {
                        K.forEach(resource.onload, function(code) {
                            try
                            {
                                (new Function(code)).apply(me);
                            }
                            catch (ex)
                            {
                                K.log(code);
                            }
                        });
                    }
                }
            };

            if ('css' in resource && !K.isEmpty(resource.css)) {
                K.Resource.loadCSS(resource.css, afterCssLoaded);
            } else {
                afterCssLoaded();
            }
        },

        _dispatchResponse: function(response) {
            if (response.suppressHandler) {
                return;
            }

            if (this._handler) {
                this._handler(response.payload, this);
            }

            if (this._finallyHandler) {
                this._finallyHandler(this);
            }
        },

        _dispatchErrorResponse: function(response) {
            if (response.suppressHandler) {
                return;
            }

            if (this._errorHandler) {
                this._errorHandler(response.error, this);
            }

            if (this._finallyHandler) {
                this._finallyHandler(this);
            }
        }
    });

    module.exports = Request;
});
